In the past, creating executable software code could be a time-consuming task. The typical code creation process involved first creating a source code program (i.e., a series of lines of program statements in a high-level computer language) with a text processing program, compiling and linking the source code (sometimes with an intermediate assembling step) to create executable code (i.e., a series of machine language instructions) for a specified computer processor, and then storing the executable code in an executable file. The executable code could then be debugged by executing the executable file on the specified computer processor to determine if the software performed its task correctly, or if instead it had one or more errors (i.e., bugs). If the executable code had errors, a software developer would modify the source code in an attempt to remove the errors, recompile the source code, and then link the recompiled code to produce a new executable file for debugging. For large software programs, this process was iteratively performed a large number of times until all known errors were removed.
In many cases, the cause of an error (e.g., a mistake in the program logic) is not obvious from executing the executable file. Various options existed for a software developer to identify errors. For example, a software developer could add print statements throughout the source code so that as the corresponding executable print instructions are executed (after compiling and linking), they would report the current progress of the execution. Knowledge of the current execution progress assists in identifying the section of the code which was executing when an error occurred. In addition to merely reporting execution progress, print statements can also display the current value of variables or source code expressions at specified points throughout the execution. Since the print statements were part of the original compilation/linking process, the variables and expressions that were part of the print statements would be evaluated in the context of the current variable scope (e.g., using the value of a local variable in a currently executing function rather than a variable with the same name in a different non-executing function), as would any other compiled code statement.
In addition to print requests, application programs known as debuggers were developed to provide additional control over execution of executable files. A debugger loads executable code into memory and then controls execution of the executable code. For example, the debugger can execute a single executable code instruction at a time. Alternately, the debugger can execute the executable code continuously until a break point designated within the debugger is reached. Such debuggers can also use additional information stored in an executable code file during the compiling and linking steps to reconstruct and display the source code lines that correspond to the instructions in the executable code. The display of the source code facilitates control by the software developer of the execution of the executable code (e.g., setting a breakpoint at a particular point in the source code). When execution of the executable code is stopped, a user can interact with the debugger to view current values of variables and expressions. In addition, some debuggers allow a user to view the effects of temporarily modifying a source code line. Nonetheless, although such debuggers can assist with locating errors in executable compiled code, recompiling and linking is needed to fix errors that are located.
In addition to the use of debuggers, other techniques have been developed to modify the functionality of compiled code without requiring a full recompilation and linking. One technique involves relinking previously compiled code with different code than was previously used for linking (e.g., using an updated Dynamic-Link Library or replacing a stubbed routine with a functional routine). In this situation, no changes are made to the previously compiled code, but changes in the overall program functionality can occur due to the different operation of the newly linked code. However, this technique is not typically useful in modifying errors in the compiled code (since the compiled code is not changed) or in flexibly adding functionality to an executable file at a desired user-specified location (since only previously specified link points can be used for the relinking).
Another technique to modify the functionality of compiled code without requiring recompilation and linking involves rewriting an executable file. Rather than modifying an existing file, rewriting involves creating an entirely new executable compiled file based on an existing executable file. Rewriting an executable file does allow new functionality to be added to an executable file at a user-specified location because new compiled instructions can be added to the new file. However, rewriting is difficult to perform without adding errors into the new file, and the specific mechanisms for adding instructions (e.g., adjusting offsets in existing instructions) typically vary on each type of processor.
When executable code is being created for an embedded system (e.g., an embedded controller for manufacturing equipment), the problems with software code creation are exacerbated. Such embedded systems may include only a CPU and memory, without having access to other standard computer system components such as a keyboard or display. In addition, standard application programs such as text processors and debuggers may not be available for an embedded system. In this environment, the source code will typically be created on a host computer system separate from the embedded target system. This allows a user application such as a text processor to create the source code. The source code is then compiled for the target embedded computer system and transferred (e.g., over a network) to the embedded system for execution and debugging. When an error occurs during execution of the executable code on the embedded system, the lack of standard computer system components and application programs on the target system make it extremely difficult to determine the cause of the error. Even obtaining information about the current state of the execution at the time of the error is typically difficult. Moreover, even if such information is available, it will need to be transferred back to the host computer system where modifications to the source code can begin another compile/link/transfer/debug cycle.